package com.xuzimian.globaldemo.spring.core.ioc;

import com.xuzimian.globaldemo.spring.core.bean.SimpleBean;
import com.xuzimian.globaldemo.spring.core.bean.SimpleLazyInitBean;
import org.junit.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;

/**
 * 测试 spring Ioc容器初始化过程：
 * Spring IOC容器设计中，Bean定义载入和依赖注入是两个独立的过程。
 * <p>
 * 1.IOC容器初始化(Bean定义载入)包含三个过程:
 * (1) Resource定位过程，指BeanDefinition的资源定位。
 * (2) BeanDefunition载入的过程。载入的过程就是将用户定义好的Bean表示成IOC容器内部的数据结构。
 * (3) 向IOC容器注册这些BeanDefinition的过程，该过程通过调用BeanDefinitionRegistry接口的实现来完成的。
 * <p>
 * 2.IOC容器依赖注入过程：程序第一次调用getBean向IOC容器索取bean时候会触发
 *
 * @program: spring-framework
 * @description:
 * @author: xzm
 * @create: 2019-05-14 11:18
 **/
public class TestSpringIocInitByXml {

    @Test
    public void testBeanFactory() {
        ClassPathResource res = new ClassPathResource("Spring-SimpleBean.xml");
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        /**
         *  reader 将xml配置中bean独到IOC容器中是通过调用DefaultListableBeanFactory 类的 registerBeanDefinition方法，
         *  该方法是 覆盖的BeanDefinitionRegistry 接口方法。在DefaultListableBeanFactory 中将其存入一个名为beanDefinitionMap
         *  的ConcurrentHashMap<String, BeanDefinition> 中。
         */
        reader.loadBeanDefinitions(res);
        SimpleBean bean = (SimpleBean) factory.getBean("simpleBean");
        bean.doSomething(" : Hello World");
    }


    /**
     * 注意:在创建context对象的时候就已经完依次成了 bean定义载入和依赖注入两个过程,因此本方法直接就打印了
     * 创建bean对象输出语句。
     * <p>
     * --------------------------------------------------------------------
     * <p>
     * 无论是ClassPathXmlApplicationContext 还是 FileSystemXmlApplicationContext 器构造器代码都是完全一样
     * 的， 都是通过refresh()方法来启动IOC容器的初始化。
     * 1. context 中 载入Bean定义的 refresh 方法调用链:
     * (AbstractApplicationContext )             refresh   →
     * (AbstractApplicationContext)              obtainFreshBeanFactory →
     * (AbstractRefreshableApplicationContext)   refreshBeanFactory →
     * (AbstractXmlApplicationContext)           loadBeanDefinitions →
     * (AbstractBeanDefinitionReader)            loadBeanDefinitions →
     * <p>
     * 2.getResources() 方法调用:
     * 其中 AbstractBeanDefinitionReader 的 loadBeanDefinitions 方法中
     * Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
     * <p>
     * (1). resourceLoader 实际类型是Context(ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext)，而context继承了
     * ResourcePatternResolver 接口，在Context的抽象父类AbstractApplicationContext的构造函数中给 resourcePatternResolver 属性
     * 创建了 PathMatchingResourcePatternResolver 实例。 context (AbstractApplicationContext) 实现的ResourcePatternResolver
     * 接口方法 实际是采用装饰器设计模式，转交给了 resourcePatternResolver 属性去完成的，也就是实际调用了PathMatchingResourcePatternResolver
     * 对象。而PathMatchingResourcePatternResolver 的getResources 最终调用的是 DefaultResourceLoader 的 getResource方法。
     * <p>
     * (2). 而 DefaultResourceLoader 的 getResource 最后调用 getResourceByPath()方法。该方法实际执行在不同context又是有区别的，因为
     * FileSystemXmlApplicationContext 覆盖了 DefaultResourceLoader 父类的getResourceByPath() 方法， 通过 new FileSystemResource 来获取资源。
     * ClassPathXmlApplicationContext 则没有覆盖getResourceByPath方法，所以起调用的是DefaultResourceLoader 本身实现的getResourceByPath()
     * 方法，其是通过new ClassPathContextResource(path, getClassLoader()) 来获取资源的。
     * <p>
     * <p>
     * 备注: resourceLoader 是  ResourceLoader 接口的实例，实际是在 (AbstractXmlApplicationContext) loadBeanDefinitions方法中创建
     * XmlBeanDefinitionReader 对象时，通过 beanDefinitionReader.setResourceLoader(this); 将当前context对象
     * (ClassPathXmlApplicationContext 或 FileSystemXmlApplicationContext ) 设置给了AbstractBeanDefinitionReader父类.
     * 所以在loadBeanDefinitions 中调用getResourceLoader() 获取的ResourceLoader对象实际类型是对应Context类型。
     */
    @Test
    public void testIOCContainerLoadBeanDefinitions() {
        ApplicationContext context = new ClassPathXmlApplicationContext("Spring-SimpleBean.xml");

        System.out.println(" ------------ ");

        ApplicationContext fileContext = new FileSystemXmlApplicationContext("E:\\MyCode\\GlobalDemo\\spring\\core\\src\\main\\resources\\Spring-SimpleBean.xml");

    }

    /**
     * 测试使用BeanFactory的IOC容器初始化的过程。
     * 注意:和ApplictionContext不同的是，在创建context对象的时候就已经完依次成了
     * bean定义载入和依赖注入两个过程，因此BeanFactory需要程序第一次调用getBean
     * 向IOC容器索取bean时候才会触发Bean依赖。(可参见本方法的控制台打印顺序)
     */
    @Test
    public void testBeanFactoryInitialization() {
        ClassPathResource res = new ClassPathResource("Spring-SimpleBean.xml");
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
        reader.loadBeanDefinitions(res);

        System.out.println("---1. IOC 容器初始化完成 : Bean定义被载入。");

        // 程序第一次调用getBean向IOC容器索取bean时候会触发
        SimpleBean bean = (SimpleBean) factory.getBean("simpleBean");

        System.out.println("---2. IOC 容器依赖注入bean完成");

        bean.doSomething(" : Hello World");
    }

    /**
     * 测试懒加载的Bean初始化顺序。
     * SimpleLazyInitBean 设置了 lazy-init="true",所以其只有在程序调用getBean获取该bean时
     * 才会被创建。
     * 注意：ApplicationContext 在refresh()方法中调用了finishBeanFactoryInitialization(beanFactory)
     * 方法实例化所有的(non-lazy-init) 单例，而finishBeanFactoryInitialization 中主要也是执行了
     * BeanFactory.getBean方法来完成 IOC容器依赖注入过程。所以ApplicationContext 和 BeanFactory 触发
     * 赖注入行为 是一致的。
     *
     */
    @Test
    public void testLazyInitBean() {
        ApplicationContext context = new ClassPathXmlApplicationContext("Spring-SimpleBean.xml");
        System.out.println("---1.lazy bean还未被创建。");
        // 程序程序调用getBean获取该bean时 会创建设置了懒加载的bean
        SimpleLazyInitBean bean = (SimpleLazyInitBean) context.getBean("simpleLazyInitBean");
        bean.doSomething(" :  Hello World");
    }
}
